package fastcsv;

import static fastcsv.utils.common.*;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;

public class FastCsvReader1 {
	public static final String FILEPATH = "C:\\data\\an_example.csv";
	public static String file1 = "C:\\data\\an_example.csv";
	public static String file2 = "C:\\data\\output.csv";
	public static String file3 = "C:\\Users\\Administrator\\wmap_all_gb.csv";
	public static String file4 = "C:\\data\\test1.csv";
	public static String file5 = "C:\\data\\xmap_test.csv";
	public static String file6 = "C:\\Users\\hao\\eclipse_workspace_202009\\Helloworld\\src\\FastCsvReader1.java";
	public static String file7 = ".\\data\\simple1.csv";
	public static String file8 = ".\\data\\simple2.csv";
	public static String file9 = ".\\data\\xmap_test.csv";

	public static void main(String[] args) throws Exception {
		// if the length of args is 0, then enter test mode.
		if (args.length == 0) {
			file9 = "C:\\data\\wmap_all_16_17_au.csv";
			test_mode(file9);
			return;
		}

		File fin = new File(args[0]);
		if (!fin.exists()) {
			System.out.printf("The input file '%s' does not exist.\n", file3);
			return;
		}

		if (fin.isFile()) {
			get_file_lines_test(args[0]);
		} else if (fin.isDirectory()) {
			for (String subfile : fin.list()) {
				String fullpath = args[0] + "/" + subfile;
				if (new File(fullpath).isFile())
					get_file_lines_test(fullpath);
			}
		}

	}

	private static void test_mode(String filepath) throws Exception {
		File file = new File(filepath);
		if (!file.exists()) {
			System.out.printf("Test Mode: file %s does not exist.\n", filepath);
			return;
		}

		get_file_lines_test(filepath);
	}

	public static void get_file_lines_test(String filepath) throws Exception {
		long t1 = System.currentTimeMillis();
		long linecount = count_lines(filepath, 10000);
		long t2 = System.currentTimeMillis();
		System.out.printf("%s,%s,", filepath, get_time());
		System.out.printf("%s,%d,%d,%d\n", get_time(), t2 - t1, linecount, new File(filepath).length());
	}

	public static char br = '\n'; // new line
	public static char ch = 'a'; // a char
	public static char sp = 's'; // separator
	public static char qt = '\"'; // quote "
	public static char cm = ','; // quote
	
	public static long count_lines(String filepath, int size) throws Exception {
		/****************************
		 * Preparing Process
		 **********************************/
		RandomAccessFile infile = new RandomAccessFile(filepath, "r");
		File outputfile = new File(filepath + ".buffer");
		if (outputfile.exists())
			outputfile.delete();
		RandomAccessFile outfile = new RandomAccessFile(outputfile.getAbsolutePath(), "rw");
		StringBuilder sb = new StringBuilder(4 * 1024 * 1024); // 4 MB of memory

		byte[] bts = new byte[size];
		int line_count = 0;

		boolean inquotes = false;
		char pre = br; // 上一个是br表示开始就是新行
		char cur = ch; // 当前的是什么不重要，因为第一次读取后就会被新值更新

		long filesize = infile.length();
		long reading = 0;
		long segmentId = -1; // each segment has an ID.
		long currentRowStartPos = 0;
		long end = 0;
		long total_items = 0;
		long absStartPos = 0; // 每次开始的绝对位置，每次完成读取后更新位置。
		int last_readcount = 0;
		
		/****************************
		 * Looping Process
		 **********************************/
		while (true) {
			// Reading bytes from file
			absStartPos += last_readcount;

			// 读取文件, 统计读取时间，并累加到 reading 上
			long t0 = System.currentTimeMillis();
			last_readcount = infile.read(bts);
			reading += System.currentTimeMillis() - t0;

			// end loop if no more byte read
			if (last_readcount <= 0)
				break;

			segmentId++;

			// states: [in quotes]
			ArrayList<Long> list = new ArrayList<Long>();
			for (int i = 0; i < last_readcount; i++) {
				cur = (char) bts[i];
				if (pre == br) {
					total_items += list.size();
					list.clear();

					// 第1次不用添加
					if (line_count > 0) {
						// 先结束当前行
						sb.append((absStartPos + i - 1 - currentRowStartPos) + ";");
						// 更新当前行的起始位置。
						currentRowStartPos = absStartPos + i;
					}
					line_count++;
					// 再添加新行
					if (sb.length() > sb.capacity() / 2) {
						outfile.write(toBytes(sb));
					}
					sb.append(currentRowStartPos + ",0,");
				}

				if (pre == cm) {
					int offset = (int) (absStartPos + i - currentRowStartPos);
					list.add((long) offset);
					sb.append(offset + ",");
				}

				// in quotes
				if (inquotes) {
					if (pre == qt) {
						if (cur == qt)
							pre = ch;
						else {
							inquotes = false;
							if (cur == br) {
								end = absStartPos + i;
								pre = br;
							} else if (cur == cm)
								pre = cm;
							else
								pre = sp;
						}
					} else
						pre = cur == qt ? qt : ch;
				}

				// normal state
				else {
					if (cur == qt) {
						inquotes = true;
						pre = sp;
						continue;
					} else if (cur == br) {
						end = absStartPos + i;
						pre = br;
					} else if (cur == cm)
						pre = cm;
					else
						pre = ch;
				}
			}
		}

		/**************************** PostProcess **********************************/
		line_count++;
		sb.append((absStartPos - currentRowStartPos) + ";");
		outfile.write(toBytes(sb));
		infile.close();
		outfile.close();
		
//		System.out.println(outputfile.getCanonicalPath());
//		System.out.printf("Reading Time: %d ms, speed: %.2f GB/s\n", reading, filesize / 1048576.0 / reading);
//		System.out.printf("items count: %d, line count: %d, seperator: \n%s\n", total_items, line_count, sb.toString().replace(';', '\n'));
		return line_count;
	}

	private static byte[] toBytes(StringBuilder sb) throws IOException {
		String[] rows = sb.toString().split(";");
		int rowSize = rows[0].split(",").length * 2 + 6;
		byte[] bts = new byte[rows.length * rowSize];
		int pos = 0;
		for (String row : rows) {
			String[] values = row.split(",");
			if(values.length != 7) {
				sb.delete(0, sb.length());
				sb.append(row);
				break;
			}
			byte[] bts0 = longToBytes(Long.parseLong(values[0]));
			for (int j = 0; j < bts0.length; j++)
				bts[pos + j] = bts0[j];
			pos += bts0.length;

			for (int i = 1; i < values.length; i++) {
				byte[] bts1 = shortToBytes(Integer.parseInt(values[i]));
				bts[pos + 0] = bts1[0];
				bts[pos + 1] = bts1[1];
				pos += 2;
			}
		}
		sb.delete(0, sb.length());
		return bts;
	}

	public static byte[] longToBytes(long data) {
		byte[] res = new byte[8];
		for (int i = 0; i < res.length; i++) {
			res[i] = (byte) (data >> 8 * i);
		}
		return res;
	}

	public static long bytesToLong(byte[] args) {
		if (args == null || args.length != 8)
			throw (new NullPointerException("非法集合或集合为空！"));

		long res = 0;
		for (int i = args.length - 1; i >= 0; i--) {
			res = (res << 8) | (args[i] & 0xff);
		}
		return res;
	}

	public static byte[] integerToBytes(int data) {
		byte[] res = new byte[4];
		for (int i = 0; i < res.length; i++) {
			res[i] = (byte) (data >> 8 * i);
		}
		return res;
	}

	public static byte[] shortToBytes(int data) {
		byte[] res = new byte[2];
		res[0] = (byte) (data >> 0);
		res[1] = (byte) (data >> 8);
		return res;
	}

	public static int bytesToInteger(byte[] args) {
		if (args == null || args.length != 4)
			throw (new NullPointerException("非法集合或集合为空！"));

		int res = 0;
		for (int i = args.length - 1; i >= 0; i--) {
			res = (res << 8) | (args[i] & 0xff);
		}
		return res;
	}

	public static void show_statatistics(String filepath, long linecount, long time) {
		File file = new File(filepath);
		double t = time / 1000.0; // ms -> s
		System.out.printf("Size: %s\n", format_filesize(file.length()));
		System.out.printf("Rows: %d\n", linecount);
		System.out.printf("Time: %.2fs\n", t);
		System.out.printf("Speed: %sB/s\n", format_filesize(file.length() / t));
		System.out.printf("Speed: %srow/s\n", format_filesize(linecount / t));
	}

}